package Runde2;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.TreeMap;

public abstract class LVA {
	private final int lvaNr;
	private String title;
	private Person owner;
	private boolean zombie;
	private GregorianCalendar earliestReg;
	private GregorianCalendar latestReg;
	private GregorianCalendar deReg;
	private int maxMembers;
	private HashSet<String> students;
	private TreeMap<Long, Integer> appointments;
	private int[] requirements;
	private ArrayList<Integer> predecessors;
	
	
	/**
	 * creates new lva
	 * 
	 * @param lvaNr - unique lva number
	 * @param title - lva title
	 * @param owner - owner of lva
	 * @param earliestReg - earliest date to register  
	 * @param latestReg - latest date to register
	 * @param deReg - latest date to deregister
	 * @param maxMembers - maximum amount of members. if unlimited value is integer.MAX_VALUE
	 * @param predecessors - predecessors of the LVA (earlier semester, no longer existing equivalent LVA)
	 * @param requirements - requirements students should meet 
	 * @throws InvalidLVAException - numerous error cases (end of registration precede it's beginning, ...)
	 * 
	 */
	public LVA(int lvaNr, String title, Person owner, GregorianCalendar earliestReg, 
			GregorianCalendar latestReg, GregorianCalendar deReg, int maxMembers, ArrayList<Integer> predecessors, int[] requirements) throws InvalidLVAException {
		if(zombie) {
			throw new InvalidLVAException("The course was deleted.");
		}
		if (latestReg.before(earliestReg)) {
			throw new InvalidLVAException("End of registration must not precede its beginning");
		}
		if (deReg.before(latestReg)) {
			throw new InvalidLVAException("End of deregistration must not precede the end of registration");
		}
		if (deReg.before(earliestReg)) {
			throw new InvalidLVAException("End of deregistration must not precede the beginning of registration");
		}
		if (!(owner instanceof Professor)){
			throw new InvalidLVAException("Owner must be a professor");
		}
			this.lvaNr = lvaNr;
			this.title = title;
			this.owner = owner;
			this.zombie = false;
			this.earliestReg = earliestReg;
			this.latestReg = latestReg;
			this.deReg = deReg;
			this.maxMembers = maxMembers;
			this.students = new HashSet<String>();
			this.appointments = new TreeMap<Long, Integer>();
			this.requirements = requirements;
			this.predecessors = predecessors;
			this.predecessors.add(lvaNr);
	}
	
	/**
	 * register a new student to lva
	 * 
	 * @param s - student to register
	 * @param d - time of registration (for testcases)
	 * @throws CorruptDeRegistrationException - numerous error cases (LVA is already full, student is already registered ...)
	 * @throws InsufficientPermissionsException When the student enters a wrong password.
	 * @throws InvalidLVAException If the course is deleted.
	 */
	public void register(String matrNr, GregorianCalendar d, String pw) throws CorruptDeRegistrationException, InsufficientPermissionsException, InvalidLVAException {
		if(zombie) {
			throw new InvalidLVAException("You can't register with a deleted course.");
		}
		
		if(!StudentManager.getStud(matrNr).validate(pw)) {
			throw new InsufficientPermissionsException("You have entered a wrong password.");
		}
		
		if(d.before(earliestReg) || d.after(latestReg)) {
			throw new CorruptDeRegistrationException("not within registration period");
		}
		if(maxMembers<=students.size()) {
			throw new CorruptDeRegistrationException("the course is full");
		}
		if (students.contains(matrNr)) {
			throw new CorruptDeRegistrationException("student is already registered");
		}
		Student tempStud = StudentManager.getStud(matrNr);
		if(!tempStud.validatePassedLVAs(requirements)) {		
			throw new CorruptDeRegistrationException("student doesn't meet the requirements");
		}
		for (Integer i: predecessors) {
			if (tempStud.getTries(i) >= 3){ 
				throw new CorruptDeRegistrationException("student already took the maximum amount of activities (3)");
			}
		}
		students.add(matrNr);						
	}
	
	/**
	 * deregister a student from lva
	 * 
	 * @param s - student to deregister
	 * @param d - time of deregistration
	 * @throws CorruptDeRegistrationException - end of deregistration has already passed
	 * @throws InsufficientPermissionsException When the student enters a wrong password.
	 */
	public void deregister(String matrNr, GregorianCalendar d, String pw) throws CorruptDeRegistrationException, InsufficientPermissionsException {
		if(!StudentManager.getStud(matrNr).validate(pw)) {
			throw new InsufficientPermissionsException("You have entered a wrong password.");
		}
		
		if(d.before(deReg))
			students.remove(matrNr);
		else
			throw new CorruptDeRegistrationException("The end of deregistration has already passed");
	}
	
	/**
	 * Returns a list of the students to the owner.
	 * 
	 * @param pw Password of the owner.
	 * @return A list of the students.
	 * @throws InsufficientPermissionsException When a wrong password is entered.
	 */
	public HashSet<String> getStudents(String pw) throws InsufficientPermissionsException {
		if (owner.validate(pw)) {
			return students;
		} else {
			throw new InsufficientPermissionsException("Only the owner can access the course's students.");
		}
	}
	
	/**
	 * @return - LVA's owner
	 */
	public Person getOwner() {
		return owner;
	}
	
	/**
	 * @return - lvaNr
	 */
	public int getNr() {
		return lvaNr;
	}
	
	/**
	 * Getter for the "zombie" flag.
	 * 
	 * @return True or False depending on whether the LVA is a zombie or not.
	 */
	public boolean getStatus() {
		return zombie;
	}
	
	
	/**
	 * @return - lvaNr
	 */
	public String getTitle() {
		return title;
	}
	
	
	/**
	 * Returns a readable output of the list of students to the owner.
	 * 
	 * @param pw The owner's password.
	 * @return A readable output of the course's participants.
	 * @throws InsufficientPermissionsException When a wrong password is entered.
	 */
	public String getStudentList(String pw) throws InsufficientPermissionsException {
		if (owner.validate(pw)) {
			String output = "";
			for(String matrNr : students) {
				output = matrNr + " | " + StudentManager.getStud(matrNr).toString() + "\n"; 
			}
			return output;
		} else {
			throw new InsufficientPermissionsException("Only the owner can view the course's students.");
		}
	}
	
	/**
	 * @return - readable output of LVA
	 */
	public String toString() {
		return  "#: " + lvaNr + " title: " + title + " " + "(" + this.getClass().getSimpleName() + ")" + "\n\tearliest registration: " +
		earliestReg.getTime() + "\n\tlatest registration: " + 
		latestReg.getTime() + "\n\tderegistration until: " + 
		deReg.getTime() + "\n\t# of students: " + students.size() + " of " + maxMembers + "\n";
	}
	
	/**
	 * "Kills" a course so it can't be interacted with.
	 * 
	 * @param pw The owner's password.
	 * @throws InsufficientPermissionsException When a wrong password is entered.
	 */
	public void kill(String pw) throws InsufficientPermissionsException {
		if (!owner.validate(pw)) {
			throw new InsufficientPermissionsException("Only the owner can kill a course.");
		}
		sendMessage("The course was deleted.");
		zombie = true;
	}
	
	/**
	 * Restores a course.
	 * 
	 * @param pw The owner's password.
	 * @throws InsufficientPermissionsException When a wrong password is entered.
	 */
	public void revive(String pw) throws InsufficientPermissionsException{
		if (!owner.validate(pw)) {
			throw new InsufficientPermissionsException("Only the owner can restore a course.");
		}
		sendMessage("The course was restored.");
		zombie = false;
	}
	
	/**
	 * changes lva title
	 * 
	 * @param t - new title
	 * @param password - owner's password
	 * @throws InsufficientPermissionsException - wrong password
	 * @throws InvalidLVAException - invalid title
	 */
	public void changeTitle(String t, String password) throws InsufficientPermissionsException, InvalidLVAException {
		if(owner.validate(password)) {
			if(t.length() > 0) {
				title = t;
				sendMessage("lva title changed");
			} else {
				throw new InvalidLVAException("invalid title");
			}
		} else {
			throw new InsufficientPermissionsException("wrong password");
		}
	}
	
	/**
	 * changes earliest registration date
	 * 
	 * @param d - new date
	 * @param password - owner's password
	 * @throws InsufficientPermissionsException - wrong password
	 * @throws InvalidLVAException - invalid date
	 */
	public void changeEarliestReg(GregorianCalendar d, String password) throws InsufficientPermissionsException, InvalidLVAException {
		if(owner.validate(password)) {
			if(d.before(latestReg)) {
				earliestReg = d;
				sendMessage("earliest date of registration changed");
			} else {
				throw new InvalidLVAException("inavlid date");
			}
		} else {
			throw new InsufficientPermissionsException("wrong password");
		}
	}
	
	/**
	 * changes latest registration date
	 * 
	 * @param d - new date
	 * @param password - owner's password
	 * @throws InsufficientPermissionsException - wrong password
	 * @throws InvalidLVAException - invalid date
	 */
	public void changeLatestReg(GregorianCalendar d, String password) throws InsufficientPermissionsException, InvalidLVAException {
		if(owner.validate(password)) {
			if(d.after(earliestReg)&&d.before(deReg)) {
				latestReg = d;
				sendMessage("latest date of registration changed");
			} else {
				throw new InvalidLVAException("inavlid date");
			}
		} else {
			throw new InsufficientPermissionsException("wrong password");
		}
	}
	
	/**
	 * changes deregistration date
	 * 
	 * @param d - new date
	 * @param password - owner's password
	 * @throws InsufficientPermissionsException - wrong password
	 * @throws InvalidLVAException - invalid date
	 */
	public void changeDeReg(GregorianCalendar d, String password) throws InsufficientPermissionsException, InvalidLVAException {
		if(owner.validate(password)) {
			if(d.after(latestReg)) {
				deReg = d;
				sendMessage("latest date of deregistration changed");
			} else {
				throw new InvalidLVAException("inavlid date");
			}
		} else {
			throw new InsufficientPermissionsException("wrong password");
		}
	}
	
	/**
	 * changes maximum amount of members
	 * 
	 * @param m - new amount
	 * @param password - owner's password
	 * @throws InsufficientPermissionsException - wrong password
	 * @throws InvalidLVAException - invalid amount (<=0)
	 */
	public void changeMaxMembers(int m, String password) throws InsufficientPermissionsException, InvalidLVAException {
		if(owner.validate(password)) {
			if(m > 0) {
				maxMembers = m;
				sendMessage("maximum amount of members changed");
			} else {
				throw new InvalidLVAException("inavlid amount of members");
			}
		} else {
			throw new InsufficientPermissionsException("wrong password");
		}
	}
	
	/**
	 * changes requirements
	 * 
	 * @param r - new requirements
	 * @param password - owner's password
	 * @throws InsufficientPermissionsException - wrong password
	 */
	public void changeRequirements(int[] r, String password) throws InsufficientPermissionsException {
		if(owner.validate(password)) {
			requirements = r;
			sendMessage("lva requirements changed");
		} else {
			throw new InsufficientPermissionsException("wrong password");
		}
	}
	
	/**
	 * sends message to all registered students 
	 * 
	 * @param text - message to send
	 */
	private void sendMessage(String text)  {
		Message m = new Message(lvaNr+": "+title, new GregorianCalendar(), text);
		for(String matrNr : students) {
			StudentManager.getStud(matrNr).sendMessage(m);
		}
	}
	
	/**
	 * set single appointment
	 * 
	 * @param date - date and time of appointment
	 * @param duration - duration of appointment in hours
	 * @throws InsufficientPermissionsException - wrong owner's password given
	 */
	public void createAppointment(GregorianCalendar date, int duration, String password) throws InsufficientPermissionsException {
		if (!owner.validate(password)) {
			throw new InsufficientPermissionsException("Wrong password. (lva-owner)");
		}
		appointments.put(date.getTimeInMillis(), duration);
	}
	
	/**
	 * set weekly appointments
	 * 
	 * @param begin - begin date and time of appointment
	 * @param end - end date of appointment
	 * @param duration - duration of appointment in hours
	 * @throws InsufficientPermissionsException - wrong owner's pw given
	 */
	public void createWeeklyAppointments(GregorianCalendar begin, GregorianCalendar end, int duration, String password) throws InsufficientPermissionsException {
		if (!owner.validate(password)) {
			throw new InsufficientPermissionsException("Wrong password. (lva-owner)");
		}
		while(begin.before(end)) {
			appointments.put(begin.getTimeInMillis(), duration);
			begin.add(Calendar.WEEK_OF_YEAR, 1);
		}
	}
	
	/**
	 * remove single appointment
	 * 
	 * @param date - date and time of appointment
	 * @throws InsufficientPermissionsException - wrong owner's password given
	 */
	public void removeAppointment(GregorianCalendar date, String password) throws InsufficientPermissionsException {
		if (!owner.validate(password)) {
			throw new InsufficientPermissionsException("Wrong password. (lva-owner)");
		}
		appointments.remove(date.getTimeInMillis());
		sendMessage("appointment on "+date.getTime()+" is removed.");
	}
	
	/**
	 * Checks whether the specified student is registered with this course.
	 * 
	 * @param matrNr The unique number of the student to check.
	 * @return If the student is registered or not.
	 */
	public boolean existsStudent(String matrNr) {
		if (students.contains(matrNr)) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Kicks a student out of the course
	 * 
	 * @param matrNr - Student's matrNr
	 * @param pw - lva-owner's pw
	 */
	public void kick(String matrNr, String pw) throws InsufficientPermissionsException {
		if (!owner.validate(pw)) {
			throw new InsufficientPermissionsException("Wrong password. (lva-owner");
		}
		students.remove(matrNr);
	}
	
	/**
	 * Returns a readable form of all created appointments
	 * 
	 * @return FISH
	 */
	public String getAppointments() {
		String result = "";
		long key = appointments.firstKey();
		int value = appointments.get(key);
		GregorianCalendar temp = new GregorianCalendar();
		while(key > 0) {
			temp.setTimeInMillis(key);
			result += ""+temp.getTime()+" - ";
			temp.add(Calendar.HOUR_OF_DAY, value);
			result += temp.getTime()+"\n";
			if(appointments.higherKey(key) != null) {
				key = appointments.higherKey(key);
				value = appointments.get(key);
			} else
				key = -1;
		}
		return result;
	}

}
